/*
 * Tranquil Java Integrated Development Environment
 *
 * The GNU General Public License Version 3
 *
 * Copyright (C) 2021 Autumn Lamonte
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] ⚧ Trans Liberation Now
 * @version 1
 */
package tjide.ui;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.List;
import java.util.ResourceBundle;

import gjexer.TExceptionDialog;
import gjexer.event.TMouseEvent;
import gjexer.ttree.TTreeItem;
import gjexer.ttree.TTreeViewWidget;

import tjide.debugger.DebugValue;
import tjide.debugger.Scope;

/**
 * LocalsWindowTreeItem is a single item in a local variables tree view.
 */
public class LocalsWindowTreeItem extends TTreeItem {

    /**
     * Translated strings.
     */
    private static ResourceBundle i18n = ResourceBundle.getBundle(LocalsWindowTreeItem.class.getName());

    // ------------------------------------------------------------------------
    // Constants --------------------------------------------------------------
    // ------------------------------------------------------------------------

    private enum Type {

        /**
         * Top-level item.
         */
        ROOT,

        /**
         * Local variables root.
         */
        LOCALS,

        /**
         * Object fields root.
         */
        OBJECT,

        /**
         * Static fields root.
         */
        STATIC,

        /**
         * Value in the tree.
         */
        VALUE,

    }

    // ------------------------------------------------------------------------
    // Variables --------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * The containing treeview.
     */
    private TTreeViewWidget view;

    /**
     * The scope associated with the tree view.
     */
    private Scope scope;

    /**
     * The debug process value associated with this item.
     */
    private DebugValue value;

    /**
     * The type of item in the tree.
     */
    private Type type = Type.ROOT;

    // ------------------------------------------------------------------------
    // Constructors -----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Public constructor, used for the virtual root target.
     *
     * @param view root TTreeViewWidget
     */
    public LocalsWindowTreeItem(final TTreeViewWidget view) {
        super(view.getTreeView(), i18n.getString("variablesInScope"), true);

        this.view = view;

        type = Type.ROOT;
    }

    /**
     * Private constructor, used for expanding items.
     *
     * @param view root TTreeViewWidget
     * @param scope the debug process scope
     * @param type the type of item in the tree
     */
    private LocalsWindowTreeItem(final TTreeViewWidget view, final Scope scope,
        final Type type) {

        super(view.getTreeView(), i18n.getString("unavailable"), false);

        this.view = view;
        this.scope = scope;
        this.type = type;

        assert (type != Type.ROOT);

        switch (type) {
        case ROOT:
            setText(i18n.getString("unavailable"));
            break;
        case LOCALS:
            setText(i18n.getString("locals"));
            setExpandable(true);
            break;
        case OBJECT:
            setText(MessageFormat.format(i18n.getString("object"),
                    scope.getObjectClassName(),
                    String.format("0x%08x", scope.getObjectHashCode())));
            setExpandable(true);
            break;
        case STATIC:
            setText(MessageFormat.format(i18n.getString("static"),
                    scope.getObjectClassName()));
            setExpandable(true);
            break;
        }
    }

    // ------------------------------------------------------------------------
    // Event handlers ---------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Method that subclasses can override to handle mouse button
     * double-clicks.
     *
     * @param mouse mouse button event
     */
    @Override
    public void onMouseDoubleClick(final TMouseEvent mouse) {
        LocalsWindow window = (LocalsWindow) getWindow();
        if (value != null) {
            if (value.canModify()) {
                window.modifyValue();
            }
        }
    }

    // ------------------------------------------------------------------------
    // TTreeItem --------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Called when this item is expanded or collapsed.  this.expanded will be
     * true if this item was just expanded from a mouse click or keypress.
     */
    @Override
    public void onExpand() {
        getChildren().clear();
        if (!isExpanded() || !isExpandable()) {
            return;
        }

        if (scope == null) {
            return;
        }

        // Local variables
        if (type == Type.LOCALS) {
            List<DebugValue> values = scope.getLocalVariables();
            for (DebugValue value: values) {
                LocalsWindowTreeItem item = new LocalsWindowTreeItem(view,
                    scope, Type.VALUE);
                view.getChildren().remove(item);
                item.level = level + 1;
                item.value = value;
                item.setText(MessageFormat.format(i18n.getString("value"),
                        value.getName(), value.getType(), value.getValue()));
                getChildren().add(item);
                item.setExpandable(value.isCompound());
            }
            return;
        }

        // Object variables
        if (type == Type.OBJECT) {
            List<DebugValue> values = scope.getObjectVariables();
            for (DebugValue value: values) {
                LocalsWindowTreeItem item = new LocalsWindowTreeItem(view,
                    scope, Type.VALUE);
                view.getChildren().remove(item);
                item.level = level + 1;
                item.value = value;
                item.setText(MessageFormat.format(i18n.getString("value"),
                        value.getName(), value.getType(), value.getValue()));
                getChildren().add(item);
                item.setExpandable(value.isCompound());
            }
            return;
        }

        // Static reference variables
        if (type == Type.STATIC) {
            List<DebugValue> values = scope.getStaticVariables();
            for (DebugValue value: values) {
                LocalsWindowTreeItem item = new LocalsWindowTreeItem(view,
                    scope, Type.VALUE);
                view.getChildren().remove(item);
                item.level = level + 1;
                item.value = value;
                item.setText(MessageFormat.format(i18n.getString("value"),
                        value.getName(), value.getType(), value.getValue()));
                getChildren().add(item);
                item.setExpandable(value.isCompound());
            }
            return;
        }

        if (type == Type.VALUE) {
            // Compound type
            List<DebugValue> values = value.getSubValues();
            if (values != null) {
                for (DebugValue value: values) {
                    LocalsWindowTreeItem item = new LocalsWindowTreeItem(view,
                        scope, Type.VALUE);
                    view.getChildren().remove(item);
                    item.level = level + 1;
                    item.value = value;
                    item.setText(MessageFormat.format(i18n.getString("value"),
                            value.getName(),
                            value.getType(), value.getValue()));
                    getChildren().add(item);
                    item.setExpandable(value.isCompound());
                }
            }
            return;
        }

    }

    // ------------------------------------------------------------------------
    // LocalsWindowTreeItem ---------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Clear this item.  Note package private access.
     */
    void clear() {
        assert (type == Type.ROOT);
        scope = null;
        getParent().getChildren().removeAll(getChildren());
        getChildren().clear();
        getTreeView().setSelected(this, true);
        view.reflowData();
    }

    /**
     * Set the scope for this item.  Note package private access.
     *
     * @param scope the new scope
     */
    void setScope(final Scope scope) {
        clear();

        LocalsWindowTreeItem item = new LocalsWindowTreeItem(view, scope,
            Type.LOCALS);
        item.level = this.level + 1;
        getChildren().add(item);
        item.setExpanded(true);
        item.onExpand();
        item = new LocalsWindowTreeItem(view, scope, Type.OBJECT);
        item.level = this.level + 1;
        getChildren().add(item);
        item.setExpanded(true);
        item.onExpand();
        item = new LocalsWindowTreeItem(view, scope, Type.STATIC);
        item.level = this.level + 1;
        getChildren().add(item);
        item.setExpanded(true);
        item.onExpand();
        view.reflowData();
    }

    /**
     * Get the value.
     *
     * @return value, or null if not available
     */
    public DebugValue getValue() {
        return value;
    }

    /**
     * Update displayed value.  Note package private access.
     */
    void updateValue() {
        setText(MessageFormat.format(i18n.getString("value"),
                value.getName(), value.getType(), value.getValue()));
    }

}
